pub mod req;
pub mod rsp;

use crate::constant::c::CLIENT_LOG_TARGET;
use crate::core::entity::jump_url;
use crate::core::entity::resource_url;
use crate::core::entity::whitelist_key::WhitelistKey;
use crate::core::model::resource_version_entry::ResourceVersionEntry;
use crate::custom_filter;
use crate::custom_filter::{ChannelSettings, CustomFilter, HotUpdateSettings, WhitelistSettings};
use crate::error::{IntoWithStatus, ToStatusCode};
use crate::nested_map::NestedMap;
use crate::router::common_router::req::{GetUpdateInfoRequest, PostLogRequest};
use crate::router::common_router::rsp::GetInnerIpResponse;
use rsp::GetUpdateInfoResponse;
use share::core::channel::Channel;
use share::core::device_id::DeviceId;
use share::core::native_version::NativeVersion;
use share::core::{native_version, resource_version};
use snafu::{OptionExt, ResultExt, Snafu};
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tracing::info;
use warp::http::StatusCode;
use warp::reply::with_status;
use warp::{Filter, Rejection, Reply};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Failed to detect the ip of the client"))]
    IpNotFound {},
    #[snafu(display("Failed to get channel setting by channel: {channel:?}"))]
    NoChannelSetting { channel: Channel },
    #[snafu(display(
        "Failed to get hot udpate setting by channel: {channel:?} and native version: {native_version}"
    ))]
    NoHotUpdateSetting {
        channel: Channel,
        native_version: NativeVersion,
    },
    #[snafu(display("Invalid native version. -> {source}"))]
    NativeVersion { source: native_version::Error },
    #[snafu(display("Invalid resource version. -> {source}"))]
    ResourceVersion { source: resource_version::Error },
    #[snafu(display("Invalid resource url. -> {source}"))]
    ResourceUrl { source: resource_url::Error },
    #[snafu(display("Invalid jump url. -> {source}"))]
    JumpUrl { source: jump_url::Error },
}

impl ToStatusCode for Error {
    fn status_code(&self) -> StatusCode {
        match self {
            Error::IpNotFound { .. } => StatusCode::NOT_FOUND,
            Error::NoChannelSetting { .. } => StatusCode::NOT_FOUND,
            Error::NoHotUpdateSetting { .. } => StatusCode::NOT_FOUND,
            Error::NativeVersion { .. } => StatusCode::INTERNAL_SERVER_ERROR,
            Error::ResourceVersion { .. } => StatusCode::INTERNAL_SERVER_ERROR,
            Error::ResourceUrl { .. } => StatusCode::INTERNAL_SERVER_ERROR,
            Error::JumpUrl { .. } => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

type Result<T> = std::result::Result<T, Error>;

struct Implementation {}

impl Implementation {
    async fn get_ip(
        addr: Option<SocketAddr>,
        real_ip: Option<IpAddr>,
        forwarded_for: Option<String>,
    ) -> Result<IpAddr> {
        real_ip.or(addr.map(|a| a.ip())).context(IpNotFoundSnafu {})
    }

    async fn get_update_info(
        req: GetUpdateInfoRequest,
        addr: Option<SocketAddr>,
        real_ip: Option<IpAddr>,
        forwarded_for: Option<String>,
        channel_settings: Arc<RwLock<ChannelSettings>>,
        hot_update_settings: Arc<RwLock<HotUpdateSettings>>,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<GetUpdateInfoResponse> {
        let channel = req.channel;
        let native_version = req.version;

        let guard = channel_settings.read().await;
        let channel_setting = guard
            .get(&channel)
            .context(NoChannelSettingSnafu { channel })?;

        let channel = channel_setting.name;
        let min_native_version = channel_setting.native_version.min();
        let latest_native_version = channel_setting.native_version.latest();
        let jump_url = channel_setting.jump_url.clone();

        drop(guard);

        let hot_update_setting = hot_update_settings
            .get_by_channel_and_native_version(&channel, &native_version)
            .await;
        let Ok(hot_update_setting) = hot_update_setting else {
            return Ok(GetUpdateInfoResponse {
                channel,
                min_native_version,
                latest_native_version,
                jump_url,
                raw_resource_version: None,
                default_resource_version: None,
                resource_url: None,
            });
        };

        let resource_url = hot_update_setting.resource_url.clone();

        let mut resource_version = hot_update_setting.resource_version.clone();

        let whitelist = whitelist_settings
            .get_by_channel_and_native_version(&channel, &native_version)
            .await;

        let mut whitelist_entry: Option<ResourceVersionEntry> = None;

        if let Ok(whitelist) = whitelist {
            let device_id = req.device_id.and_then(|s| DeviceId::new(&s).ok());
            if let Some(device_id) = device_id {
                let key = WhitelistKey::DeviceId(device_id);
                if let Some(entry) = whitelist.get(&key) {
                    whitelist_entry = Some(entry);
                }
            }

            if let None = whitelist_entry {
                let ip = real_ip.or(addr.map(|a| a.ip()));
                if let Some(ip) = ip {
                    let key = WhitelistKey::Ip(ip);
                    if let Some(entry) = whitelist.get(&key) {
                        resource_version = entry;
                    }
                }
            }
        }

        if let Some(entry) = whitelist_entry {
            resource_version = entry;
        }

        let response = GetUpdateInfoResponse {
            channel,
            min_native_version,
            latest_native_version,
            jump_url,
            raw_resource_version: Some(resource_version.raw.clone()),
            default_resource_version: Some(resource_version.default.clone()),
            resource_url: Some(resource_url),
        };

        Ok(response)
    }

    async fn set_inner_ip(
        addr: Option<SocketAddr>,
        real_ip: Option<IpAddr>,
        inner_ip: Arc<RwLock<Option<IpAddr>>>,
    ) -> Result<()> {
        let ip = real_ip
            .or(addr.map(|a| a.ip()))
            .context(IpNotFoundSnafu {})?;

        let mut guard = inner_ip.write().await;
        *guard = Some(ip);

        Ok(())
    }

    async fn delete_inner_ip(inner_ip: Arc<RwLock<Option<IpAddr>>>) -> Result<()> {
        let mut guard = inner_ip.write().await;
        *guard = None;

        Ok(())
    }

    async fn get_inner_ip(inner_ip: Arc<RwLock<Option<IpAddr>>>) -> Result<GetInnerIpResponse> {
        let guard = inner_ip.read().await;

        Ok(GetInnerIpResponse { ip: guard.clone() })
    }
}

struct Router {}

impl Router {
    fn ping() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::get()
            .and(warp::path("ping"))
            .and(warp::path::end())
            .then(|| async { with_status("pong", StatusCode::OK) })
    }

    fn get_ip_router(
        filter: Arc<CustomFilter>,
    ) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Copy {
        warp::get()
            .and(warp::path("ip"))
            .and(warp::path::end())
            .and(warp::addr::remote())
            .and(warp::header::optional::<IpAddr>("X-Real-Ip"))
            .and(warp::header::optional::<String>("X-Forwarded-For"))
            .then(|addr, real_ip, forwarded_for| async move {
                match Implementation::get_ip(addr, real_ip, forwarded_for).await {
                    Ok(ip) => with_status(ip.to_string(), StatusCode::OK),
                    Err(e) => with_status(e.to_string(), e.status_code()),
                }
            })
    }

    pub fn get_update_info(
        filter: Arc<CustomFilter>,
    ) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::get()
            .and(warp::path("update_info"))
            .and(warp::path::end())
            .and(warp::query::<GetUpdateInfoRequest>())
            .and(warp::addr::remote())
            .and(warp::header::optional::<IpAddr>("X-Real-Ip"))
            .and(warp::header::optional::<String>("X-Forwarded-For"))
            .and(filter.channel_settings())
            .and(filter.hot_update_settings())
            .and(filter.whitelist_settings())
            .then(
                |req,
                 addr,
                 real_ip,
                 forwarded_for,
                 channel_setting,
                 hot_update_setting,
                 whitelist_setting| async move {
                    Implementation::get_update_info(
                        req,
                        addr,
                        real_ip,
                        forwarded_for,
                        channel_setting,
                        hot_update_setting,
                        whitelist_setting,
                    )
                    .await
                    .into_with_status()
                },
            )
            .with(filter.cors())
    }

    pub fn post_log() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::post()
            .and(warp::path("log"))
            .and(warp::path::end())
            .and(warp::addr::remote())
            .and(warp::header::optional::<IpAddr>("X-Real-Ip"))
            .and(warp::header::optional::<String>("X-Forwarded-For"))
            .and(warp::body::json())
            .then(
                |addr, real_ip, forwarded_for, req: PostLogRequest| async move {
                    info!(target: CLIENT_LOG_TARGET, ?addr, ?real_ip, ?forwarded_for, ?req);
                    with_status("", StatusCode::OK)
                },
            )
    }

    pub fn post_inner_ip(
        filter: Arc<CustomFilter>,
    ) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::post()
            .and(warp::path("inner_ip"))
            .and(warp::path::end())
            .and(warp::addr::remote())
            .and(warp::header::optional::<IpAddr>("X-Real-Ip"))
            .and(filter.inner_ip())
            .then(|addr, real_ip, inner_ip| async move {
                Implementation::set_inner_ip(addr, real_ip, inner_ip)
                    .await
                    .into_with_status()
            })
    }

    pub fn delete_inner_ip(
        filter: Arc<CustomFilter>,
    ) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::delete()
            .and(warp::path("inner_ip"))
            .and(warp::path::end())
            .and(filter.inner_ip())
            .then(|inner_ip| async move {
                Implementation::delete_inner_ip(inner_ip)
                    .await
                    .into_with_status()
            })
    }

    pub fn get_inner_ip(
        filter: Arc<CustomFilter>,
    ) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::get()
            .and(warp::path("inner_ip"))
            .and(warp::path::end())
            .and(filter.inner_ip())
            .then(|inner_ip| async move {
                Implementation::get_inner_ip(inner_ip)
                    .await
                    .into_with_status()
            })
    }
}

pub fn get_all_router(
    filter: Arc<custom_filter::CustomFilter>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    Router::ping()
        .or(Router::get_ip_router(filter.clone()))
        .or(Router::get_update_info(filter.clone()))
        .or(Router::post_log())
        .or(Router::post_inner_ip(filter.clone()))
        .or(Router::delete_inner_ip(filter.clone()))
        .or(Router::get_inner_ip(filter.clone()))
}
